package com.anonymous.server;

import java.io.IOException;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import com.anonymous.message.ChatMsg;
import com.anonymous.message.CheckPaidUserMsg;
import com.anonymous.message.CheckPaidUserResultMsg;
import com.anonymous.message.ErrorMsg;
import com.anonymous.message.HelloMsg;
import com.anonymous.message.LoginMsg;
import com.anonymous.message.LoginResult;
import com.anonymous.message.Message;
import com.anonymous.message.PayMsg;
import com.anonymous.message.PayResultMsg;
import com.anonymous.message.RegisterMsg;
import com.anonymous.message.RegisterResult;
import com.anonymous.model.User;
import com.anonymous.util.LoggerUtils;
import com.anonymous.util.NetworkUtils;

public class ServerThread extends Thread {
	private Socket connectionSocket = null;
	private User connectionUser = null;
	private Server server = null;

	private NetworkUtils networkUtils;
	String loggingHeader;
	private LoggerUtils logger = new LoggerUtils();

	// constructor
	public ServerThread(Socket socket, Server server)
			throws NoSuchAlgorithmException, NoSuchPaddingException {
		networkUtils = new NetworkUtils();
		this.server = server;
		this.connectionSocket = socket;
		this.loggingHeader = Thread.currentThread().getName() + " "
				+ connectionSocket.getInetAddress().toString();
	}

	@Override
	public void run() {
		// object to be send back to client
		Message objectToSend;

		// object received from client
		Message objectReceived;

		// n is used for tracking and debugging
		int n = 0;

		// this loops until conncection is lost with client
		try {
			while (true) {
				n++;
				System.out.println("Begin" + n);
				System.out.println(server.getUserList());

				// receive object from client
					objectReceived = networkUtils.receive(connectionSocket);
				if (objectReceived.getFlag().equals("LOGOUT")){
					if(connectionSocket!=null)logger.LogNormal("Logout Success: "+connectionUser.getUserName()+" IP Address: "+connectionUser.getIpAddress());
					break;
				}
				objectToSend = handleMessage(objectReceived, connectionSocket
						.getInetAddress().getHostAddress());
				if (objectToSend != null)
					networkUtils.send(objectToSend, connectionSocket);

				System.out.println(loggingHeader + " End" + n);
			}
		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			/*
			 * The control flow will end up here upon 1. Arrival of LOGOUT
			 * message, 2. Disconnection of client socket, 3. Or any kind of
			 * exceptions Logout logic to be here.
			 */
			try {
				if (connectionSocket != null) {
					System.out.println(loggingHeader + " closing socket");
					
					connectionSocket.close();
					connectionSocket = null;
				}
				if (connectionUser != null) {
					System.out.println(loggingHeader + " : User "
							+ connectionUser.getUserName()
							+ " has been logged out");
					server.getUserList().removeUser(connectionUser);
				}
				if(connectionSocket!=null)
				this.logger.LogNormal("DISCONNECTED: IP Address: "+ this.connectionSocket.getInetAddress() + "; Port: " + this.connectionSocket.getPort());
				
			} catch (IOException e) {
				System.out.println(loggingHeader + " : prints error below");
				e.printStackTrace();
			}
		}
		// parse object received from client and get object to send back to
		// client

	}

	// handles various client requests
	private Message handleMessage(Message msgReceived, String ipAddress)
			throws NoSuchAlgorithmException, InvalidKeyException,
			IllegalBlockSizeException, BadPaddingException, IOException {
		if (msgReceived == null) {
			return null;
		}
		// parse received object to string array
		// String[] message = ((String) (msgReceived)).split(",");

		// get command
		String flag = msgReceived.getFlag();

		// login request
		if ("LOGIN".equals(flag)) {
			System.out.println(flag);

			return (validateLogin(msgReceived, ipAddress));
		}
		// new user register request
		else if (flag.equals("REGISTER")) {
			return (register(msgReceived, ipAddress));
		} 
		else if ("CHECK_PAID_MSG".equals(flag)) {
			return checkIfUserPaid(msgReceived);
		}
		//Pay
		else if("PAY_MSG".equals(flag)){
			return tryToPay();
		}
		
		else if (flag.equals("CHAT_MSG")) {
			System.out.println(((ChatMsg) msgReceived).getMessage());
			return new HelloMsg();
		} else {
			return new ErrorMsg();
		}
	}

	private Message tryToPay() {
		// TODO Auto-generated method stub
		PayResultMsg payResultMsg;
		if(server.getDbUtils().isPaidUser(connectionUser.getUserName()))payResultMsg = new PayResultMsg(false);
		else {
			server.getDbUtils().changeUserType(connectionUser.getUserName(), true);
			payResultMsg = new PayResultMsg(true);
		}
		return payResultMsg;
	}

	private Message checkIfUserPaid(Message msgReceived) {
		// TODO Auto-generated method stub
		CheckPaidUserMsg checkPaidUserMsg = (CheckPaidUserMsg) msgReceived;
		CheckPaidUserResultMsg checkPaidUserResultMsg = new CheckPaidUserResultMsg();
		checkPaidUserResultMsg.setUserNameToCheck(checkPaidUserMsg
				.getUserNameToCheck());

		checkPaidUserResultMsg.setPaidUser(server.getDbUtils().isPaidUser(
				checkPaidUserMsg.getUserNameToCheck()));
		
		return checkPaidUserResultMsg;
	}

	// validates user login
	private Message validateLogin(Message msgReceived, String ipAddress) {
		// debugging purposes
		LoginMsg loginMsg = (LoginMsg) msgReceived;
		String userName = loginMsg.getUsername();
		String passwordHash = loginMsg.getPasswordHash();
		System.out.println(userName);
		System.out.println(passwordHash);
		if(userName.equals("")||passwordHash.equals(""))return new LoginResult(false);
		// connect to database to validate username and password hash.
		if (server.getDbUtils().login(userName, passwordHash)) {
			// if user isn't already logged in
			if (!server.getUserList().isUser(userName)) {
				// record the user of this thread
				connectionUser = new User(loginMsg.getCertificate(), userName,
						ipAddress);

				// add the current user of this thread to the server's Userlist
				server.getUserList().addUser(connectionUser);
			}
			if(connectionSocket!=null)this.logger.LogNormal("Login SUCCESS: " + loginMsg.getUsername() +"; IP Address: "+ this.connectionSocket.getInetAddress() + "; Port: " + this.connectionSocket.getPort());
			return new LoginResult(true);
		} else {
			if(connectionSocket!=null)this.logger.LogNormal("Login FAIL: " + loginMsg.getUsername() +"; IP Address: "+ this.connectionSocket.getInetAddress() + "; Port: " + this.connectionSocket.getPort());			return new LoginResult(false);
		}
	}

	// register a new user
	private Message register(Message msgReceived, String ipAddress) {
		RegisterMsg registerMsg = (RegisterMsg) msgReceived;
		String userName = registerMsg.getUsername();
		String passwordHash = registerMsg.getPasswordHash();

		System.out.println(userName);
		System.out.println(passwordHash);
		// connect to database to try to register
		if(userName.equals("")||passwordHash.equals(""))return new LoginResult(false);
		if (server.getDbUtils().register(userName, registerMsg.isPaidUser(),
				passwordHash)) {
			// record the user of this thread
			connectionUser = new User(registerMsg.getCertificate(), userName,
					ipAddress);

			// add the current user of this thread to the server's Userlist
			server.getUserList().addUser(connectionUser);
			if(connectionSocket!=null)this.logger.LogNormal("Register SUCCESS: " + registerMsg.getUsername() +"; IP Address: "+ this.connectionSocket.getInetAddress() + "; Port: " + this.connectionSocket.getPort());
			return new RegisterResult(true);
		} else
			if(connectionSocket!=null)this.logger.LogSevere("Register FAILED: " + registerMsg.getUsername() +"; IP Address: "+ this.connectionSocket.getInetAddress() + "; Port: " + this.connectionSocket.getPort());
		return new RegisterResult(false);
	}

}
